using System;
using System.IO;
using System.Threading.Tasks;
using NLog;

namespace FileSyncApp
{
    /// <summary>
    /// 文件同步服务类，负责监控源文件夹的变化并实时同步到目标文件夹
    /// 支持文件的创建、修改、删除、重命名等操作的实时同步
    /// 使用FileSystemWatcher进行文件系统监控，确保高效的实时同步
    /// </summary>
    public class FileSyncService
    {
        /// <summary>
        /// NLog日志记录器，用于记录同步过程中的详细信息和错误
        /// </summary>
        private static readonly Logger logger = LogManager.GetCurrentClassLogger();
        
        /// <summary>
        /// 文件系统监控器，用于监控源文件夹的文件变化
        /// </summary>
        private FileSystemWatcher? _watcher;
        
        /// <summary>
        /// 源文件夹路径，被监控的文件夹
        /// </summary>
        private string _sourcePath = string.Empty;
        
        /// <summary>
        /// 目标文件夹路径，同步的目标位置
        /// </summary>
        private string _targetPath = string.Empty;
        
        /// <summary>
        /// 同步服务运行状态标志
        /// </summary>
        private bool _isRunning = false;

        /// <summary>
        /// 日志消息事件，用于向UI层传递日志信息
        /// </summary>
        public event Action<string>? LogMessage;
        
        /// <summary>
        /// 文件数量变化事件，用于通知UI更新文件计数
        /// </summary>
        public event Action<int>? FileCountChanged;

        /// <summary>
        /// 获取同步服务的运行状态
        /// </summary>
        public bool IsRunning => _isRunning;

        /// <summary>
        /// 启动文件同步服务
        /// 执行初始同步并开始监控源文件夹的变化
        /// </summary>
        /// <param name="sourcePath">源文件夹路径</param>
        /// <param name="targetPath">目标文件夹路径</param>
        public void StartSync(string sourcePath, string targetPath)
        {
            if (_isRunning)
            {
                LogMessage?.Invoke("同步服务已在运行中");
                return;
            }

            if (!Directory.Exists(sourcePath))
            {
                LogMessage?.Invoke($"源文件夹不存在: {sourcePath}");
                return;
            }

            _sourcePath = sourcePath;
            _targetPath = targetPath;

            // 确保目标文件夹存在
            if (!Directory.Exists(_targetPath))
            {
                Directory.CreateDirectory(_targetPath);
                LogMessage?.Invoke($"创建目标文件夹: {_targetPath}");
            }

            // 初始同步
            Task.Run(() => InitialSync());

            // 设置文件监控
            SetupFileWatcher();

            _isRunning = true;
            LogMessage?.Invoke($"开始监控文件夹: {_sourcePath}");
            logger.Info($"文件同步服务启动 - 源: {_sourcePath}, 目标: {_targetPath}");
        }

        /// <summary>
        /// 停止文件同步服务
        /// 释放文件监控器资源并更新运行状态
        /// </summary>
        public void StopSync()
        {
            if (!_isRunning) return;

            _watcher?.Dispose();
            _watcher = null;
            _isRunning = false;

            LogMessage?.Invoke("文件同步服务已停止");
            logger.Info("文件同步服务停止");
        }

        /// <summary>
        /// 设置文件系统监控器
        /// 配置监控参数并注册事件处理程序
        /// </summary>
        private void SetupFileWatcher()
        {
            _watcher = new FileSystemWatcher(_sourcePath)
            {
                IncludeSubdirectories = true,
                NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | 
                              NotifyFilters.FileName | NotifyFilters.DirectoryName
            };

            _watcher.Created += OnFileChanged;
            _watcher.Changed += OnFileChanged;
            _watcher.Deleted += OnFileDeleted;
            _watcher.Renamed += OnFileRenamed;

            _watcher.EnableRaisingEvents = true;
        }

        /// <summary>
        /// 处理文件创建和修改事件
        /// 当源文件夹中的文件被创建或修改时触发
        /// </summary>
        /// <param name="sender">事件发送者</param>
        /// <param name="e">文件系统事件参数</param>
        private async void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            try
            {
                await Task.Delay(100); // 防止文件被占用
                await SyncFile(e.FullPath);
            }
            catch (Exception ex)
            {
                LogMessage?.Invoke($"同步文件失败: {e.Name} - {ex.Message}");
                logger.Error(ex, $"同步文件失败: {e.FullPath}");
            }
        }

        /// <summary>
        /// 处理文件删除事件
        /// 当源文件夹中的文件被删除时，同步删除目标文件夹中的对应文件
        /// </summary>
        /// <param name="sender">事件发送者</param>
        /// <param name="e">文件系统事件参数</param>
        private void OnFileDeleted(object sender, FileSystemEventArgs e)
        {
            try
            {
                string relativePath = Path.GetRelativePath(_sourcePath, e.FullPath);
                string targetFile = Path.Combine(_targetPath, relativePath);

                if (File.Exists(targetFile))
                {
                    File.Delete(targetFile);
                    LogMessage?.Invoke($"删除文件: {relativePath}");
                    logger.Info($"删除文件: {targetFile}");
                }
                else if (Directory.Exists(targetFile))
                {
                    Directory.Delete(targetFile, true);
                    LogMessage?.Invoke($"删除文件夹: {relativePath}");
                    logger.Info($"删除文件夹: {targetFile}");
                }
            }
            catch (Exception ex)
            {
                LogMessage?.Invoke($"删除失败: {e.Name} - {ex.Message}");
                logger.Error(ex, $"删除失败: {e.FullPath}");
            }
        }

        /// <summary>
        /// 处理文件重命名事件
        /// 当源文件夹中的文件被重命名时，同步重命名目标文件夹中的对应文件
        /// </summary>
        /// <param name="sender">事件发送者</param>
        /// <param name="e">重命名事件参数，包含旧名称和新名称</param>
        private async void OnFileRenamed(object sender, RenamedEventArgs e)
        {
            try
            {
                string oldRelativePath = Path.GetRelativePath(_sourcePath, e.OldFullPath);
                string newRelativePath = Path.GetRelativePath(_sourcePath, e.FullPath);
                string oldTargetPath = Path.Combine(_targetPath, oldRelativePath);
                string newTargetPath = Path.Combine(_targetPath, newRelativePath);

                if (File.Exists(oldTargetPath))
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(newTargetPath)!);
                    File.Move(oldTargetPath, newTargetPath);
                    LogMessage?.Invoke($"重命名文件: {oldRelativePath} -> {newRelativePath}");
                }
                else if (Directory.Exists(oldTargetPath))
                {
                    Directory.Move(oldTargetPath, newTargetPath);
                    LogMessage?.Invoke($"重命名文件夹: {oldRelativePath} -> {newRelativePath}");
                }
                else
                {
                    await SyncFile(e.FullPath);
                }

                logger.Info($"重命名: {e.OldFullPath} -> {e.FullPath}");
            }
            catch (Exception ex)
            {
                LogMessage?.Invoke($"重命名失败: {e.Name} - {ex.Message}");
                logger.Error(ex, $"重命名失败: {e.FullPath}");
            }
        }

        /// <summary>
        /// 执行初始同步
        /// 在开始监控之前，将源文件夹的所有内容同步到目标文件夹
        /// </summary>
        /// <returns>异步任务</returns>
        private async Task InitialSync()
        {
            try
            {
                LogMessage?.Invoke("开始初始同步...");
                await SyncDirectory(_sourcePath, _targetPath);
                LogMessage?.Invoke("初始同步完成");
            }
            catch (Exception ex)
            {
                LogMessage?.Invoke($"初始同步失败: {ex.Message}");
                logger.Error(ex, "初始同步失败");
            }
        }

        /// <summary>
        /// 递归同步整个目录结构
        /// 包括所有子文件夹和文件，并更新文件计数
        /// </summary>
        /// <param name="sourceDir">源目录路径</param>
        /// <param name="targetDir">目标目录路径</param>
        /// <returns>异步任务</returns>
        private async Task SyncDirectory(string sourceDir, string targetDir)
        {
            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
            }

            // 同步文件
            foreach (string file in Directory.GetFiles(sourceDir))
            {
                await SyncFile(file);
            }

            // 递归同步子文件夹
            foreach (string dir in Directory.GetDirectories(sourceDir))
            {
                string dirName = Path.GetFileName(dir);
                string targetSubDir = Path.Combine(targetDir, dirName);
                await SyncDirectory(dir, targetSubDir);
            }

            // 更新文件计数
            int fileCount = Directory.GetFiles(_sourcePath, "*", SearchOption.AllDirectories).Length;
            FileCountChanged?.Invoke(fileCount);
        }

        /// <summary>
        /// 同步单个文件
        /// 检查文件是否需要更新，如果需要则复制到目标位置
        /// 通过比较文件的最后修改时间和大小来判断是否需要更新
        /// 包含重试机制处理文件访问冲突
        /// </summary>
        /// <param name="sourceFile">源文件路径</param>
        /// <returns>异步任务</returns>
        private async Task SyncFile(string sourceFile)
        {
            const int maxRetries = 5;
            const int baseDelayMs = 500;
            
            for (int attempt = 0; attempt <= maxRetries; attempt++)
            {
                try
                {
                    if (!File.Exists(sourceFile)) return;

                    string relativePath = Path.GetRelativePath(_sourcePath, sourceFile);
                    string targetFile = Path.Combine(_targetPath, relativePath);
                    string? targetDir = Path.GetDirectoryName(targetFile);

                    if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
                    {
                        Directory.CreateDirectory(targetDir);
                    }

                    // 检查文件是否需要更新
                    if (File.Exists(targetFile))
                    {
                        var sourceInfo = new FileInfo(sourceFile);
                        var targetInfo = new FileInfo(targetFile);

                        if (sourceInfo.LastWriteTime <= targetInfo.LastWriteTime &&
                            sourceInfo.Length == targetInfo.Length)
                        {
                            return; // 文件未更改
                        }
                    }

                    // 复制文件
                    await Task.Run(() => File.Copy(sourceFile, targetFile, true));
                    LogMessage?.Invoke($"同步文件: {relativePath}");
                    logger.Info($"同步文件: {sourceFile} -> {targetFile}");
                    return; // 成功，退出重试循环
                }
                catch (IOException ioEx) when (attempt < maxRetries && IsFileInUseException(ioEx))
                {
                    // 文件被占用，等待后重试
                    int delayMs = baseDelayMs * (int)Math.Pow(2, attempt); // 指数退避
                    string fileName = Path.GetFileName(sourceFile);
                    
                    if (attempt == 0)
                    {
                        LogMessage?.Invoke($"文件被占用，正在重试: {fileName} (尝试 {attempt + 1}/{maxRetries + 1})");
                    }
                    else
                    {
                        LogMessage?.Invoke($"重试中: {fileName} (尝试 {attempt + 1}/{maxRetries + 1})");
                    }
                    
                    logger.Warn($"文件访问冲突，等待 {delayMs}ms 后重试: {sourceFile} - {ioEx.Message}");
                    await Task.Delay(delayMs);
                }
                catch (UnauthorizedAccessException uaEx) when (attempt < maxRetries)
                {
                    // 权限问题，短暂等待后重试
                    int delayMs = baseDelayMs;
                    string fileName = Path.GetFileName(sourceFile);
                    
                    LogMessage?.Invoke($"权限问题，正在重试: {fileName} (尝试 {attempt + 1}/{maxRetries + 1})");
                    logger.Warn($"权限访问问题，等待 {delayMs}ms 后重试: {sourceFile} - {uaEx.Message}");
                    await Task.Delay(delayMs);
                }
                catch (Exception ex)
                {
                    // 其他异常，记录并退出
                    string fileName = Path.GetFileName(sourceFile);
                    LogMessage?.Invoke($"同步文件失败: {fileName} - {ex.Message}");
                    logger.Error(ex, $"同步文件失败: {sourceFile}");
                    return;
                }
            }
            
            // 所有重试都失败了
            string finalFileName = Path.GetFileName(sourceFile);
            LogMessage?.Invoke($"同步文件最终失败: {finalFileName} - 已达到最大重试次数 ({maxRetries + 1})");
            logger.Error($"同步文件最终失败，已达到最大重试次数: {sourceFile}");
        }

        /// <summary>
        /// 判断是否为文件被占用的异常
        /// </summary>
        /// <param name="ioException">IO异常</param>
        /// <returns>是否为文件被占用异常</returns>
        private static bool IsFileInUseException(IOException ioException)
        {
            const int ERROR_SHARING_VIOLATION = 32;
            const int ERROR_LOCK_VIOLATION = 33;
            
            int errorCode = ioException.HResult & 0xFFFF;
            return errorCode == ERROR_SHARING_VIOLATION || 
                   errorCode == ERROR_LOCK_VIOLATION ||
                   ioException.Message.Contains("being used by another process") ||
                   ioException.Message.Contains("另一个程序正在使用此文件");
        }
    }
}